/*
* Lilith - a log event viewer.
* Copyright (C) 2007-2013 Joern Huxhorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Copyright 2007-2013 Joern Huxhorn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.huxhorn.lilith.tracing;
import de.huxhorn.sulky.formatting.SafeString;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
public class TracingAspect
{
public static final String TRACED_CLASS_MDC_KEY = "tracedClass";
public static final String TRACED_METHOD_MDC_KEY = "tracedMethod";
public static final String ENTERING_MARKER_NAME = "ENTERING";
public static final String EXITING_MARKER_NAME = "EXITING";
public static final String THROWING_MARKER_NAME = "THROWING";
public static final String TRACE_MARKER_NAME = "TRACE";
private static final Marker ENTERING_MARKER = MarkerFactory.getDetachedMarker(ENTERING_MARKER_NAME);
private static final Marker EXITING_MARKER = MarkerFactory.getDetachedMarker(EXITING_MARKER_NAME);
private static final Marker THROWING_MARKER = MarkerFactory.getDetachedMarker(THROWING_MARKER_NAME);
private static final Marker TRACE_MARKER = MarkerFactory.getDetachedMarker(TRACE_MARKER_NAME);
static
{
ENTERING_MARKER.add(TRACE_MARKER);
EXITING_MARKER.add(TRACE_MARKER);
THROWING_MARKER.add(TRACE_MARKER);
}
private String loggerName;
private Logger logger;
private boolean showingParameterValues;
private boolean usingShortClassName;
private boolean showingModifiers;
private ProfilingHandler profilingHandler;
public TracingAspect()
{
showingParameterValues = true;
usingShortClassName = false;
showingModifiers = false;
profilingHandler = new BasicProfilingHandler();
}
public boolean isShowingParameterValues()
{
return showingParameterValues;
}
public void setShowingParameterValues(boolean showingParameterValues)
{
this.showingParameterValues = showingParameterValues;
}
public boolean isUsingShortClassName()
{
return usingShortClassName;
}
public void setUsingShortClassName(boolean usingShortClassName)
{
this.usingShortClassName = usingShortClassName;
}
public boolean isShowingModifiers()
{
return showingModifiers;
}
public void setShowingModifiers(boolean showingModifiers)
{
this.showingModifiers = showingModifiers;
}
public ProfilingHandler getProfilingHandler()
{
return profilingHandler;
}
public void setProfilingHandler(ProfilingHandler profilingHandler)
{
this.profilingHandler = profilingHandler;
}
public String getLoggerName()
{
return loggerName;
}
public void setLoggerName(String loggerName)
{
this.loggerName = loggerName;
if(loggerName != null)
{
logger = LoggerFactory.getLogger(loggerName);
}
else
{
logger = LoggerFactory.getLogger(TracingAspect.class);
}
}
public String toString()
{
return "TracingAspect{loggerName="+loggerName+", showingParameterValues="+ showingParameterValues +", usingShortClassName="+ usingShortClassName +", showingModifiers="+showingModifiers+", profilingHandler="+profilingHandler+"}";
}
public Object trace(ProceedingJoinPoint call) throws Throwable
{
if(logger == null)
{
setLoggerName(null);
// this initializes the logger
}
Signature signature=call.getSignature();
Class<?> clazz = signature.getDeclaringType();
Object theTarget = call.getTarget();
if(theTarget != null)
{
clazz = theTarget.getClass();
}
String fullClassName = clazz.getName();
String methodName = signature.getName();
StringBuilder msg=new StringBuilder();
if(showingModifiers)
{
msg.append(Modifier.toString(signature.getModifiers())).append(" ");
}
if(usingShortClassName)
{
msg.append(clazz.getSimpleName());
}
else
{
msg.append(fullClassName);
}
msg.append(".").append(methodName);
String methodBaseName = msg.toString();
if(signature instanceof MethodSignature)
{
MethodSignature methodSignature=(MethodSignature)signature;
msg.append("(");
if(showingParameterValues)
{
Object[] args=call.getArgs();
boolean first=true;
for(Object arg:args)
{
if(first)
{
first=false;
}
else
{
msg.append(", ");
}
msg.append(SafeString.toString(arg,
SafeString.StringWrapping.ALL, SafeString.StringStyle.GROOVY, SafeString.MapStyle.GROOVY));
}
}
else
{
Method method=methodSignature.getMethod();
Class<?>[] parameterTypes = method.getParameterTypes();
boolean first=true;
for(Class<?> param : parameterTypes)
{
if(first)
{
first=false;
}
else
{
msg.append(", ");
}
msg.append(param.getSimpleName());
}
if(method.isVarArgs())
{
int length=msg.length();
msg.delete(length-2, length); // cut of existing []
msg.append("...");
}
}
msg.append(")");
}
String methodSignatureString=msg.toString();
String previousClass = MDC.get(TRACED_CLASS_MDC_KEY);
String previousMethod = MDC.get(TRACED_METHOD_MDC_KEY);
long nanoSeconds=0;
try
{
MDC.put(TRACED_CLASS_MDC_KEY, fullClassName);
MDC.put(TRACED_METHOD_MDC_KEY, methodName);
if(logger.isInfoEnabled(ENTERING_MARKER)) logger.info(ENTERING_MARKER, "{} entered.", methodSignatureString);
Object result;
nanoSeconds=System.nanoTime();
result=call.proceed();
nanoSeconds=System.nanoTime()-nanoSeconds;
profile(methodBaseName, methodSignatureString, nanoSeconds);
if(result == null || !showingParameterValues)
{
if(logger.isInfoEnabled(EXITING_MARKER)) logger.info(EXITING_MARKER, "{} returned.", methodSignatureString);
}
else
{
if(logger.isInfoEnabled(EXITING_MARKER)) logger.info(EXITING_MARKER, "{} returned {}.", methodSignatureString, result);
}
return result;
}
catch(Throwable t)
{
nanoSeconds=System.nanoTime()-nanoSeconds;
profile(methodBaseName, methodSignatureString, nanoSeconds);
if(logger.isInfoEnabled(THROWING_MARKER)) logger.info(THROWING_MARKER, "{} failed.", methodSignatureString, t);
throw t; // rethrow
}
finally
{
if(previousClass == null)
{
MDC.remove(TRACED_CLASS_MDC_KEY);
}
else
{
MDC.put(TRACED_CLASS_MDC_KEY, previousClass);
}
if(previousMethod == null)
{
MDC.remove(TRACED_METHOD_MDC_KEY);
}
else
{
MDC.put(TRACED_METHOD_MDC_KEY, previousMethod);
}
}
}
private void profile(String methodBaseName, String fullMethodSignature, long nanoSeconds)
{
if(profilingHandler != null)
{
logger.info("{}", profilingHandler);
profilingHandler.profile(logger, methodBaseName, fullMethodSignature, nanoSeconds);
}
}
}